/*
* File: RunProgramTask.java
* Author: Daniel Rogers
* Created on Jan 16, 2008
*
*/
package gri.gridp.tasks;
import gri.tasks.Task;
//import gri.tasks.AsyncTask;
import gri.tasks.AsyncTaskController;
import gri.tasks.AbstractTaskController;
import gri.tasks.AsyncEventHandler;
import dan.util.process.ExecutionContext;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.io.File;
import org.apache.log4j.Logger;
import gri.tasks.ParameterDef;
import gri.tasks.TaskDef;
import gri.data.DataType;
import gri.data.Types;
/**
* Wrapper around a ScriptExecutor which exposes it as a task. This will
* involve the following inputs/outputs:
*
* Inputs:
* script (string)
* jobout (fileName/string) ["stdout.txt"]
* joberr (fileName/string) ["stderr.txt"]
* workdir (file) [current workdir]
*
* Outputs:
* script (file)
* exitcode (int)
* jobout (file)
* joberr (file)
* outputFiles (file list)
*
* TODO: AsyncTask support is functional. However, we don't really
* have a way to know when the script finishes (besides polling
* it) and thus can't call AsyncEventHandler when we finish.
* We might need to re-think the event handler.
*
* @author dan.rogers
*/
public class RunScriptTask implements Task { //, AsyncTask {
static Logger logger = Logger.getLogger(RunScriptTask.class);
// -------------------------------------------------------- Properties
String scriptFile;
String execCmd;
boolean deleteWorkdirWhenFreed;
// ------------------------------------------------------ Constructors
public RunScriptTask(String scriptFile, String execCmd) {
this.scriptFile = scriptFile;
this.execCmd = execCmd;
this.deleteWorkdirWhenFreed = false;
}
// --------------------------------------------------------- Accessors
public void setScriptFile(String fileName) {
this.scriptFile = fileName;
}
public String getScriptFile() {return scriptFile;}
public void setExecCommand(String cmd) {
this.execCmd = cmd;
}
public String getExecCommand() {return execCmd;}
/**
* Sets a flag indicating whether the working directory should be
* deleted when the job is freed. (default = false)
*
* NOTE: An added safety feature exists that will not allow you to
* delete the default directory "." under any circumstances.
*/
public void setDeleteDirectoryWhenFreed(boolean value) {
this.deleteWorkdirWhenFreed = value;
}
public boolean isDeleteDirectoryWhenFreed() {
return this.deleteWorkdirWhenFreed;
}
// ---------------------------------------------------- Implementation
public TaskDef getTaskDef() {
return TASK_DEF;
}
public Map execute(Map inputs) throws Exception {
ScriptExecutor executor = createScriptExecutor(inputs);
if (logger.isDebugEnabled())
logger.debug("Script executing...");
ExecutionContext ctx = executor.start();
ctx.waitForExit();
if (logger.isDebugEnabled())
logger.debug("Script execution complete: populating outputs");
Map outputs = getOutputs(executor, ctx);
if (logger.isDebugEnabled())
logger.debug("Script outputs: " + Util.toString(outputs));
return outputs;
}
public AsyncTaskController start(Map inputs, AsyncEventHandler handler) throws Exception {
ScriptExecutor executor = createScriptExecutor(inputs);
TaskController controller = new TaskController(executor);
controller.start();
return controller;
}
/**
* Creates a ScriptExecutor that is configured with the given input
* parameters.
*/
protected ScriptExecutor createScriptExecutor(Map inputs) {
ScriptExecutor executor = new ScriptExecutor(scriptFile, execCmd);
Iterator iEntries = inputs.entrySet().iterator();
Map.Entry entry;
while (iEntries.hasNext()) {
entry = (Map.Entry)iEntries.next();
String name = (String)entry.getKey();
Object value = entry.getValue();
if (name.equals(SCRIPT))
executor.setScript((String)value);
else if (name.equals(STDOUT_FILE))
executor.setOutputFileName((String)value);
else if (name.equals(STDERR_FILE))
executor.setErrorFileName((String)value);
else if (name.equals(WORKDIR))
executor.setWorkingDirectory((File)value);
else
logger.warn("Skipping unkown parameter: " + name);
}
return executor;
}
/**
* Populates the output map with output values. Output parameters
* from the class are hard-coded while those indicated by the GRIDP
* function are obtained by examining the working directory and
* applying a FileMatcher to discover indicated files.
*/
protected static Map getOutputs(ScriptExecutor executor, ExecutionContext ctx) {
Map outputs = new HashMap();
//exit code:
int exitCode = ctx.getExitCode();
outputs.put("exitcode", new Integer(exitCode));
//script, stdout, and stderr files (if they exist):
File script = executor.getScriptFile();
if (!script.exists())
script = null;
File jobout = executor.getOutputFile();
if (!jobout.exists())
jobout = null;
File joberr = executor.getErrorFile();
if (!joberr.exists())
joberr = null;
outputs.put(SCRIPT, script);
outputs.put(STDOUT_FILE, jobout);
outputs.put(STDERR_FILE, joberr);
//other files:
File [] results = executor.getFileResults();
List resultList = new ArrayList();
for (int i=0; i<results.length; i++)
resultList.add(results[i]);
outputs.put(OUTPUT_FILES, resultList);
return outputs;
}
// ----------------------------------------------------- AsyncController
class TaskController extends AbstractTaskController {
ScriptExecutor executor;
ExecutionContext ctx;
Map outputs = null;
// Constructors:
public TaskController(ScriptExecutor executor) {
this.executor = executor;
}
// Implementation:
public void start() throws Exception {
this.ctx = executor.start();
}
public Map getOutputs() {
if (isComplete()) {
if (outputs == null)
outputs = RunScriptTask.getOutputs(executor, ctx);
}
else
throw new IllegalStateException("Process has not yet completed");
return outputs;
}
public int getStatus() {
return ctx.isComplete() ?
COMPLETE : RUNNING;
}
public boolean isComplete() {
return ctx.isComplete();
}
public void kill() {
ctx.kill();
}
/*
public void free() {
if (this.deleteWorkdirWhenFreed) {
File workdir = executor.getWorkingDirectory();
if (!workdir.equals(new File("."))) {
//this.workingDirectory.delete();
Util.rmdir(workdir);
}
}
}
*/
}
// ------------------------------------------------------------ TaskDef
public static final String SCRIPT = "script";
public static final String STDOUT_FILE = "jobout";
public static final String STDERR_FILE = "joberr";
public static final String WORKDIR = "workdir";
public static final String EXIT_CODE = "exitcode";
public static final String OUTPUT_FILES = "outputFiles";
/**
* TaskDef for a RunScriptTask
*/
public static class RequestTaskDef extends TaskDef {
public RequestTaskDef(boolean useWorkdirParam) {
setInputs();
setOutputs();
}
public RequestTaskDef() {
this(false);
}
protected void setInputs() {
ParameterDef script = new ParameterDef(SCRIPT, Types.STRING, "Text of script to execute");
ParameterDef jobout = new ParameterDef(STDOUT_FILE, Types.STRING, "Name of the file for standard output");
ParameterDef joberr = new ParameterDef(STDERR_FILE, Types.STRING, "Name of the file for standard error");
ParameterDef workingDirectory = new ParameterDef(WORKDIR, Types.FILE, "Directory in which to execute script");
script.setDisplayName("Script");
jobout.setDisplayName("Standard Output File");
joberr.setDisplayName("Standard Error File");
workingDirectory.setDisplayName("Working Directory");
jobout.setDefaultValue("stdout.txt");
joberr.setDefaultValue("stderr.txt");
workingDirectory.setDefaultValue(new File("."));
addInput(script);
addInput(jobout);
addInput(joberr);
addInput(workingDirectory);
}
protected void setOutputs() {
ParameterDef exitCode = new ParameterDef(EXIT_CODE, Types.INTEGER, "Exit code returned by program");
ParameterDef jobout = new ParameterDef(STDOUT_FILE, Types.FILE, "File containing standard output from the script");
ParameterDef joberr = new ParameterDef(STDERR_FILE, Types.FILE, "File containing standard error from the script");
ParameterDef script = new ParameterDef(SCRIPT, Types.FILE, "File containing the script that was executed");
DataType FILE_LIST = Types.createListType(List.class, Types.FILE);
ParameterDef outputFiles = new ParameterDef("outputFiles", FILE_LIST, "Output files");
exitCode.setDisplayName("Exit Code");
jobout.setDisplayName("Standard Output File");
joberr.setDisplayName("Standard Error File");
script.setDisplayName("Script File");
outputFiles.setDisplayName("Output Files");
addOutput(script);
addOutput(jobout);
addOutput(joberr);
addOutput(exitCode);
addOutput(outputFiles);
}
}
public static final RequestTaskDef TASK_DEF = new RequestTaskDef();
}